﻿//--------------------------------------------------------------------------------------------------------------------------------
// <copyright file="Scheme.cs" company="Exul">
//     Copyright (c) Exul. All rights reserved.
// </copyright>
// <author>Exul</author>
//--------------------------------------------------------------------------------------------------------------------------------

namespace ExulLibrary.FiniteElementMethod
{
    using System;
    using System.Collections.Generic;

    /// <summary>Represents a interpolation scheme over a collection of values.</summary>
    public abstract class Scheme
    {
        /// <summary>Initializes a new instance of the <see cref="Scheme"/> class.</summary>
        /// <param name="layers">The layers values.</param>
        /// <exception cref="ArgumentNullException"><paramref name="layers"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException"><paramref name="layers"/> is empty collection.</exception>
        protected Scheme(IList<double> layers)
        {
            #if SAFE

            if (layers == null)
            {
                throw Mathematics.LinearAlgebra.ThrowHelper.ArgumentIsNull("layers");
            }

            if (layers.Count == 0)
            {
                throw Mathematics.LinearAlgebra.ThrowHelper.CollectionIsEmpty("layers");
            }

            #endif

            this.Layers = layers;
        }

        /// <summary>Gets the functions count that is used by the scheme.</summary>
        /// <value>The functions count that is used by the scheme.</value>
        public abstract int FunctionsCount
        {
            get;
        }

        /// <summary>Gets the value of first layer.</summary>
        /// <value>The value of first layer.</value>
        public abstract double FirstLayerValue
        {
            get;
        }

        /// <summary>Gets the value of last layer.</summary>
        /// <value>The value of last layer.</value>
        public abstract double LastLayerValue
        {
            get;
        }

        /// <summary>Gets the layers values.</summary>
        /// <value>The layers values.</value>
        public IList<double> Layers
        {
            get;
            private set;
        }

        /// <summary>Determines whether a function with the specified index is defined at the current state.</summary>
        /// <param name="functionIndex">The index of the function.</param>
        /// <returns><see langword="true"/> if the function is defined; otherwise, <see langword="false"/>.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        ///     <paramref name="functionIndex"/> is negative or greater than or equal to the functions count.
        /// </exception>
        public abstract bool IsFunctionDefined(int functionIndex);

        /// <summary>Calculates a function with the specified index in a specified point.</summary>
        /// <param name="point">The point to calculate.</param>
        /// <param name="functionIndex">The index of the function to calculate.</param>
        /// <returns>
        ///     The value of the function with the specified index in the specified point or
        ///     zero if <paramref name="point"/> is not between <see cref="FirstLayerValue"/> and
        ///     <see cref="LastLayerValue"/>.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        ///     <paramref name="functionIndex"/> is negative or greater than or equal to the functions count.
        /// </exception>
        public abstract double CalculateFunction(double point, int functionIndex);

        /// <summary>Calculates derivative of a function with the specified index in the specified point.</summary>
        /// <param name="point">The point to calculate.</param>
        /// <param name="functionIndex">The index of the function to calculate.</param>
        /// <returns>
        ///     The value of the function derivative with the specified index in the specified point or
        ///     zero if <paramref name="point"/> is not between <see cref="FirstLayerValue"/> and
        ///     <see cref="LastLayerValue"/>.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        ///     <paramref name="functionIndex"/> is negative or greater than or equal to the functions count.
        /// </exception>
        public abstract double CalculateDerivative(double point, int functionIndex);

        /// <summary>Calculates derivative of a function with the specified index on the first layer.</summary>
        /// <param name="functionIndex">The index of the function to calculate.</param>
        /// <returns>The value of the function derivative with the specified index in the specified point.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        ///     <paramref name="functionIndex"/> is negative or greater than or equal to the functions count.
        /// </exception>
        public abstract double CalculateDerivativeOnFirstLayer(int functionIndex);

        /// <summary>Moves the current state to a next layer.</summary>
        /// <returns><see langword="true"/> if the movement is successful; otherwise, <see langword="false"/>.</returns>
        public abstract bool MoveToNextLayer();

        /// <summary>Sets the current state to the initial position.</summary>
        public abstract void Reset();
    }
}